package de.imdb250junkie.imdb;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JProgressBar;

import de.imdb250junkie.utils.Movie;

/**
 * Utility class for parsing the whole top250.
 * 
 * @author André Stannek
 * @version 2010-02-11
 */
public final class Top250Reader
{
    private static final Queue<QueuedObject> WEBPAGES = new ConcurrentLinkedQueue<>();

    /**
     * masking public utility class constructor.
     */
    private Top250Reader()
    {
        /**/
    }

    /**
     * Reads and parses the top250 page, creating an Array with the 250 movies
     * in correct order.
     * 
     * @param progress
     *            a progress bar showing the update status
     * @return the top250 in correct order
     * @throws InterruptedException
     *             if process was interrupted
     * @throws MovieNotFoundException
     *             if a movie wasn't found
     * @throws IOException
     *             if a imdb page could not be read
     */
    public static Movie[] read(final JProgressBar progress)
            throws MovieNotFoundException, InterruptedException, IOException
    {
        progress.setMaximum(501);
        progress.setValue(0);
        final Movie[] top = new Movie[250];
        final List<String> ids = new ArrayList<>();
        final String page = HTMLInterface.fetch(new URL(
                "http://www.imdb.com/chart/top"));
        progress.setValue(progress.getValue() + 1);

        final Matcher matcher = Pattern.compile("href=\"/title/(tt[0-9]*)/\"",
                Pattern.MULTILINE).matcher(page);

        while (matcher.find())
        {
            ids.add(HTMLInterface.decode(matcher.group(1)).trim());
        }

        // get pages for all ids
        for (final String id : ids)
        {
            // thread for getting page
            final Thread thread = new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    run(10);
                }

                /**
                 * run method for recursive call which tries to fetch the
                 * webpage and add it to the queue.
                 * 
                 * @param tries
                 *            the tries left for fetching the webpage if this
                 *            fetching fails
                 */
                private void run(final int tries)
                {
                    try
                    {
                        try
                        {
                            // exponetial backoff
                            Thread.sleep(10000 / tries);
                        }
                        catch (final InterruptedException e)
                        {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        WEBPAGES.add(new QueuedObject(id, HTMLInterface
                                .fetch(new URL("http://www.imdb.com/title/"
                                        + id + "/"))));
                        progress.setValue(progress.getValue() + 1);
                    }
                    catch (final IOException e)
                    {
                        // recursivly call run if tries left
                        if (tries > 0)
                        {
                            Logger.getLogger("INFO")
                                    .log(Level.INFO,
                                            "recursively calling load because of IO-Exception");
                            run(tries - 1);
                        }
                        else
                        {
                            // TODO better exception handling
                            new MovieNotFoundException(
                                    "A movie could not be loaded from imdb.", e)
                                    .printStackTrace();
                        }
                    }
                }
            });
            thread.start();
            Thread.sleep(300);
        }

        // thread for creating movies
        final Thread createMovies = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                int moviesLeft = 250;
                // as long as less than 250 movies where checked
                while (moviesLeft > 0)
                {
                    // lock if movie is avilable and parse it
                    if (!WEBPAGES.isEmpty())
                    {
                        final QueuedObject mov = WEBPAGES.poll();
                        final Thread createMovie = new Thread(new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                final Movie movie = MovieMaker.getMovie(
                                        mov.imdbID, mov.page);
                                top[movie.getRank() - 1] = movie;
                                progress.setValue(progress.getValue() + 1);
                            }
                        });
                        createMovie.start();
                        moviesLeft--;
                    }
                }
            }
        });
        createMovies.start();
        // wait till all 250 movies have been added
        while (createMovies.isAlive())
        {
            Thread.sleep(5000);
        }
        return top;
    }

    /**
     * Exception to be thrown if a movie could not be found on imdb.
     * 
     * @author André Stannek
     * @version 2010-02-09
     */
    public static class MovieNotFoundException extends Exception
    {
        /**
         * generated UID
         */
        private static final long serialVersionUID = -3728110301455471182L;

        /**
         * 
         * @param message
         *            the Exception message
         */
        public MovieNotFoundException(final String message,
                final Throwable cause)
        {
            super(message, cause);
        }
    }

    /**
     * 
     * @author André Stannek
     * @version 2010-02-10
     */
    private static class QueuedObject extends Exception
    {
        /**
         * 
         */
        private static final long serialVersionUID = 4521065119580169605L;

        protected String page;

        protected String imdbID;

        public QueuedObject(final String imdbID, final String page)
        {
            this.page = page;
            this.imdbID = imdbID;
        }
    }
}
